package com.cmes.mes.audit.service;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.cmes.cache.service.CacheService;
import com.cmes.core.exception.FrameworkException;
import com.cmes.core.utils.Preconditions;
import com.cmes.core.utils.StringPool;
import com.cmes.core.utils.StringUtils;
import com.cmes.jdbc.service.GenericService;
import com.cmes.mes.audit.constant.ErrorCode;
import com.cmes.mes.audit.entity.SnGenerateEntity;
import com.cmes.mes.audit.repository.SnGenerateRepository;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 编号生成器
 *
 * @author Bruce.Gong
 * @since 1.0.0-SNAPSHOT
 */
@Slf4j
@RequiredArgsConstructor
@Service
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
public class SnGenerateService extends GenericService<SnGenerateRepository, SnGenerateEntity> {

    private final CacheService cacheService;
    private final SnGenerateShardService snGenerateShardService;

    private Map<String, SnGenerateEntity> activeMap() {
        return cacheService.getCache("mes:sn:generate", () -> this.list().stream()
                .collect(Collectors.toMap(SnGenerateEntity::getBusinessCode, Function.identity())));
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public String generate(String businessCode) {
        SnGenerateEntity generator = checkGenerator(businessCode);
        Preconditions.notEmpty(generator.getShard(), ErrorCode.SN_GENERATE_SHARD_NONE, businessCode, generator.getShard());
        return generateSn(generator, getNextSn(generator.getId(), 1, 0));
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public List<String> generate(String businessCode, int count) {
        SnGenerateEntity generator = checkGenerator(businessCode);
        Preconditions.notEmpty(generator.getShard(), ErrorCode.SN_GENERATE_SHARD_NONE, businessCode, generator.getShard());
        return generateSn(generator, getNextSn(generator.getId(), count, 0), count);
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public String generate(String businessCode, LocalDate date) {
        SnGenerateEntity generator = checkGenerator(businessCode);
        Preconditions.check(() -> !"date".equals(generator.getShard()), ErrorCode.SN_GENERATE_SHARD_NONE, businessCode, generator.getShard());
        return snGenerateShardService.generateSn(generator, date);
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public List<String> generate(String businessCode, LocalDate date, int count) {
        SnGenerateEntity generator = checkGenerator(businessCode);
        Preconditions.check(() -> !"date".equals(generator.getShard()), ErrorCode.SN_GENERATE_SHARD_NONE, businessCode, generator.getShard());
        return snGenerateShardService.generateSn(generator, date, count);
    }

    private SnGenerateEntity checkGenerator(String businessCode) {
        SnGenerateEntity generator = activeMap().get(businessCode);
        Preconditions.isNull(generator, ErrorCode.SN_GENERATE_NOT_REG, businessCode);
        return generator;
    }

    private List<String> generateSn(SnGenerateEntity generator, Long sn, int count) {
        return IntStream.range(0, count).boxed().map(v -> generateSn(generator, sn - v)).collect(Collectors.toList());
    }

    private String generateSn(SnGenerateEntity generator, Long sn) {
        Map<String, String> params = Maps.newHashMapWithExpectedSize(1);
        String snStr = generator.getSnLength() > 0 ? StringUtils.leftPad(sn.toString(), generator.getSnLength(), StringPool.ZERO) : sn.toString();
        params.put("sn", snStr);
        return StringUtils.replacePlaceholder(generator.getRule(), params);
    }

    /**
     * 获取下一个Sn号，默认重试三次
     */
    private Long getNextSn(Long generatorId, int count, int retryTimes) {
        Long maxSn = getById(generatorId).getMaxSn();
        Long nextSn = maxSn + count;
        boolean result = update(Wrappers.<SnGenerateEntity>lambdaUpdate()
                .set(SnGenerateEntity::getMaxSn, nextSn)
                .eq(SnGenerateEntity::getId, generatorId)
                .eq(SnGenerateEntity::getMaxSn, maxSn));
        if (!result) {
            if (retryTimes >= 3) {
                throw new FrameworkException("自动生成编码失败，该编码已经被其它用户占用，请稍后再操作");
            } else {
                return getNextSn(generatorId, count, retryTimes + 1);
            }
        } else {
            return nextSn;
        }
    }

}
